package io.spring.sample.graphql.jpa;
//这个包有几个文件直接拷贝来自spring-data的基础包org.springframework.data.jpa.repository.support.*
/*
 *原始包没有开放API不是public的，后续QuerydslNcExecutorImpl类无法用：只好照抄！ 打包的包路径名字也保持一致的。
 * 实际代码也就改了一行； 还搞这么多事！ 借用原始包名称缺点？ 还是决定包名也改掉。
 * 简单修改下 1: public class  修饰符 public class FetchableFluentQueryByPredicate;
 *  2：父类 FluentQuerySupport 也要复制过来的！
 *  3: 最后把 Page<R> readPage(Pageable pageable) 的 count函数改成()->0L; 整个文件只改这1行；
 *  4: 还得拷贝来 org.springframework.data.jpa.repository.support.EntityGraphFactory
 *注意：目前只有走 io.spring.sample.graphql.jpa.QuerydslNcExecutorImpl 自定义类的
 * 	findBy(Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)才能进这里运行。
* */


/*
 * Copyright 2021-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.persistence.EntityManager;

import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.util.Assert;

import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.AbstractJPAQuery;

/**
 * Immutable implementation of {@link FetchableFluentQuery} based on a Querydsl {@link Predicate}. All methods that
 * return a {@link FetchableFluentQuery} will return a new instance, not the original.
 *
 * @param <S> Domain type
 * @param <R> Result type
 * @author Greg Turnquist
 * @author Mark Paluch
 * @author Jens Schauder
 * @author J.R. Onyschak
 * @since 2.6
 */
public class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> implements FetchableFluentQuery<R> {

	private final Predicate predicate;
	private final Function<Sort, AbstractJPAQuery<?, ?>> finder;
	private final BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder;
	private final Function<Predicate, Long> countOperation;
	private final Function<Predicate, Boolean> existsOperation;
	private final EntityManager entityManager;

	public FetchableFluentQueryByPredicate(Predicate predicate, Class<S> entityType,
										   Function<Sort, AbstractJPAQuery<?, ?>> finder, BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder,
										   Function<Predicate, Long> countOperation, Function<Predicate, Boolean> existsOperation,
										   EntityManager entityManager) {
		this(predicate, entityType, (Class<R>) entityType, Sort.unsorted(), Collections.emptySet(), finder, pagedFinder,
				countOperation, existsOperation, entityManager);
	}
	//原来private 改成 public
	public FetchableFluentQueryByPredicate(Predicate predicate, Class<S> entityType, Class<R> resultType, Sort sort,
											Collection<String> properties, Function<Sort, AbstractJPAQuery<?, ?>> finder,
											BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder, Function<Predicate, Long> countOperation,
											Function<Predicate, Boolean> existsOperation,
											EntityManager entityManager) {

		super(resultType, sort, properties, entityType);
		this.predicate = predicate;
		this.finder = finder;
		this.pagedFinder = pagedFinder;
		this.countOperation = countOperation;
		this.existsOperation = existsOperation;
		this.entityManager = entityManager;
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#sortBy(org.springframework.data.domain.Sort)
	 */
	@Override
	public FetchableFluentQuery<R> sortBy(Sort sort) {

		Assert.notNull(sort, "Sort must not be null!");

		return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, this.sort.and(sort), properties,
				finder, pagedFinder, countOperation, existsOperation, entityManager);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#as(java.lang.Class)
	 */
	@Override
	public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {

		Assert.notNull(resultType, "Projection target type must not be null!");

		if (!resultType.isInterface()) {
			throw new UnsupportedOperationException("Class-based DTOs are not yet supported.");
		}

		return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, properties, finder,
				pagedFinder, countOperation, existsOperation, entityManager);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#project(java.util.Collection)
	 */
	@Override
	public FetchableFluentQuery<R> project(Collection<String> properties) {

		return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, mergeProperties(properties),
				finder, pagedFinder, countOperation, existsOperation, entityManager);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
	 */
	@Override
	public R oneValue() {

		List<?> results = createSortedAndProjectedQuery() //
				.limit(2) // Never need more than 2 values
				.fetch();

		if (results.size() > 1) {
			throw new IncorrectResultSizeDataAccessException(1);
		}

		return results.isEmpty() ? null : getConversionFunction().apply(results.get(0));
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
	 */
	@Override
	public R firstValue() {

		List<?> results = createSortedAndProjectedQuery() //
				.limit(1) // Never need more than 1 value
				.fetch();

		return results.isEmpty() ? null : getConversionFunction().apply(results.get(0));
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
	 */
	@Override
	public List<R> all() {
		return convert(createSortedAndProjectedQuery().fetch());
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<R> page(Pageable pageable) {
		return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
	 */
	@Override
	public Stream<R> stream() {

		return createSortedAndProjectedQuery() //
				.stream() //
				.map(getConversionFunction());
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
	 */
	@Override
	public long count() {
		return countOperation.apply(predicate);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
	 */
	@Override
	public boolean exists() {
		return existsOperation.apply(predicate);
	}

	private AbstractJPAQuery<?, ?> createSortedAndProjectedQuery() {

		AbstractJPAQuery<?, ?> query = finder.apply(sort);

		if (!properties.isEmpty()) {
			query.setHint(EntityGraphFactory.HINT, EntityGraphFactory.create(entityManager, entityType, properties));
		}

		return query;
	}

	private Page<R> readPage(Pageable pageable) {

		AbstractJPAQuery<?, ?> pagedQuery = pagedFinder.apply(sort, pageable);

		//增加这3行：针对默认无配置情形有点效果：query.<>project("id","indCod","cancel")起码company person表不会去查了。而对于使用我这自定做方案的就没用了。
		if (!properties.isEmpty()) {
			pagedQuery.setHint(EntityGraphFactory.HINT, EntityGraphFactory.create(entityManager, entityType, properties));
		}	//？？加这个3行有意义吗? 对应properties配置入口queryUse= queryUse.project("id","indCod","cancel"); 有关联字段比如"company.id"或"person"的:报错

		List<R> paginatedResults = convert(pagedQuery.fetch());

		//修改1行，count不要; return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(predicate));
		return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> 0L);
	}

	private List<R> convert(List<?> resultList) {

		Function<Object, R> conversionFunction = getConversionFunction();
		List<R> mapped = new ArrayList<>(resultList.size());

		for (Object o : resultList) {
			mapped.add(conversionFunction.apply(o));
		}

		return mapped;
	}

	private Function<Object, R> getConversionFunction() {
		return getConversionFunction(entityType, resultType);
	}

}

